
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdint>
#include <ctime>
#include <string>
#include <functional>
#include <Windows.h>
#include <glm/gtc/matrix_transform.hpp>
#include <MapKit/MapKit.h>
#include "CRC32.h"
#include "Paint.h"
#if defined(_DEBUG)
#include <crtdbg.h> 
#endif

std::string timestamp()
{
    SYSTEMTIME st;
    GetLocalTime(&st);
    char str[100] = { 0 };
    sprintf(str, "[%04d%02d%02d-%02d:%02d:%02d.%03d]", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
    return str;
}

void timer(std::function<void()> f, const char* name)
{
    LARGE_INTEGER t1;
    LARGE_INTEGER t2;
    LARGE_INTEGER freq;
    QueryPerformanceFrequency(&freq);
    QueryPerformanceCounter(&t1);
    f();
    QueryPerformanceCounter(&t2);
    SYSTEMTIME st;
    GetLocalTime(&st);
    printf("%s %s: %lf ms\n", timestamp().c_str(), name, double(t2.QuadPart - t1.QuadPart) / freq.QuadPart * 1000.0);
}

#if defined(_DEBUG)
double_t w = 100 * glm::pi<double>(), h = 100, r = 8;
#else
double_t w = 500 * glm::pi<double>(), h = 500, r = 8;
#endif
uint32_t ex = 4, n = 400, k = 128;

#define GEN_FN(name, type, ext) \
std::wstring name = std::wstring(L"out") \
+ std::to_wstring(int(w + 0.5)) + L"x" + std::to_wstring(int(h + 0.5)) \
+ L"_ex" + std::to_wstring(ex) \
+ L"_r" + std::to_wstring(int(r)) \
+ L"_k" + std::to_wstring(k) \
+ L"_" + std::to_wstring(seed) \
+ type + ext;

int main()
{
    {
        uint32_t ct = 0;
        timer([&]() { ct = (uint32_t)time(nullptr); }, "get time");
        uint32_t seed = 0;
        timer([&]() { seed = CRC32::Get(&ct, sizeof(uint32_t)); }, "calculate crc32 for seed");
        printf("%s seed: %u\n", timestamp().c_str(), seed);

        timer([&]() { Paint::InitializeGdiplus(); }, "initialize gdiplus");

        MapKit* mk = nullptr;
        timer([&]() {
            mk = new MapKit();
            mk->SetSize(w, h);
        }, "create map kit");
        timer([&]() { mk->Randomize(seed); }, "initialize random");
        timer([&]() { mk->GetPoisson(r, k); }, "random poisson sampling");
        timer([&]() { mk->FractalAltitude((glm::sqrt(5.0) - 1) / 2); }, "generate altitude by fractal");
        timer([&]() { mk->Triangulate(); }, "delaunay triangulate");
        printf("%s size of vertices in map kit: %u\n", timestamp().c_str(), mk->GetVertices()->size());
        printf("%s size of cells in map kit: %u\n", timestamp().c_str(), mk->GetTriangles()->size());
#if 1
        Paint* paintDelaunay = nullptr;
        timer([&]() { paintDelaunay = new Paint(); }, "create paint(delaunay)");
        timer([&]() {
            paintDelaunay->Initialize(w, h, ex);
            paintDelaunay->Clear();
            for (MKTriangle* p : *mk->GetTriangles())
            {
                paintDelaunay->DrawLine(p->A->x, p->A->y, p->B->x, p->B->y);
                paintDelaunay->DrawLine(p->B->x, p->B->y, p->C->x, p->C->y);
                paintDelaunay->DrawLine(p->C->x, p->C->y, p->A->x, p->A->y);
            }
            for (MKVertex* vt : *mk->GetVertices())
                paintDelaunay->DrawPoint(vt->x, vt->y);
        }, "draw image(delaunay) in memory");
        timer([&]() {
            GEN_FN(fnDelaunayJpeg, L".delaunay", L".jpeg");
            paintDelaunay->Save(fnDelaunayJpeg.c_str(), L"image/jpeg", 25);
        }, "save image(delaunay, jpeg)");
        timer([&]() {
            GEN_FN(fnDelaunayPng, L".delaunay", L".png");
            paintDelaunay->Save(fnDelaunayPng.c_str(), L"image/png");
        }, "save image(delaunay, png)");
        timer([&]() { delete paintDelaunay; paintDelaunay = nullptr; }, "delete paint(delaunay)");
#endif
#if 1
        Paint* paintAltitude = nullptr;
        timer([&]() { paintAltitude = new Paint(); }, "create paint(altitude)");
        timer([&]() {
            paintAltitude->Initialize(mk->GetFractalSize(), mk->GetFractalSize(), 1);
            paintAltitude->Clear();
            double maxA = (*mk->GetAltitude())[0][0], minA = (*mk->GetAltitude())[0][0];
            for (int i = 0; i < mk->GetFractalSize(); i++)
                for (int j = 0; j < mk->GetFractalSize(); j++)
                {
                    double val = (*mk->GetAltitude())[i][j];
                    if (val < minA)
                        minA = val;
                    if (val > maxA)
                        maxA = val;
                }
            if (minA == maxA)
                return;
            for (int i = 0; i < mk->GetFractalSize(); i++)
                for (int j = 0; j < mk->GetFractalSize(); j++)
                {
                    double val = (*mk->GetAltitude())[i][j];
                    unsigned char gray = unsigned char((val - minA) / (maxA - minA) * 255 + 0.5);
                    paintAltitude->SetPixel(i, j, gray, gray, gray);
                }
        }, "draw image(altitude) in memory");
        timer([&]() {
            GEN_FN(fnAltitude, L".altitude", L".png");
            paintAltitude->Save(fnAltitude.c_str(), L"image/png");
        }, "save image(altitude, png)");
        timer([&]() { delete paintAltitude; paintAltitude = nullptr; }, "delete paint(altitude)");
#endif
        timer([&]() { delete mk; mk = nullptr; }, "delete map kit");
        timer([&]() { Paint::TerminateGdiplus(); }, "terminate gdiplus");
    }
#if defined(_DEBUG)
    _CrtDumpMemoryLeaks();
#endif
    return 0;
}